package ru.spbu.math.android.cbrf.dataprovider.cbrf;

import android.content.res.Resources;
import android.util.Log;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import ru.spbu.math.android.cbrf.R;
import ru.spbu.math.android.cbrf.dataprovider.IProvider;
import ru.spbu.math.android.cbrf.dataprovider.UnavailableValueException;
import ru.spbu.math.android.cbrf.model.Category;
import ru.spbu.math.android.cbrf.model.Rate;
import ru.spbu.math.android.cbrf.model.RateType;
import ru.spbu.math.android.cbrf.model.StockItem;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Class for getting data from CBRF. This class implements IProvider
 * 
 * @author Svetlana Zemlyanskaya
 */

public class CbrfProvider implements IProvider {

	private static final String TAG = "CbrfProvider";

	private final static String URL_ENG = "http://www.cbr.ru/scripts/XML_daily_eng.asp";
	private final static String URL_DYN = "http://www.cbr.ru/scripts/XML_dynamic.asp";
	private final static String URL_MET = "http://www.cbr.ru/scripts/xml_metall.asp";

	private Resources resources;

	private Map<String, Rate> previousRate = new HashMap<String, Rate>();
	private Map<String, Rate> currentRate = new HashMap<String, Rate>();

	public CbrfProvider(Resources res) {
		resources = res;
		RateTypeStore.getInstance(res);
	}

	/**
	 * Check connection with CBRF
	 * 
	 * @return
	 */
	public boolean isUpdatableNow() {
		try {
			URL url = new URL("http://www.cbr.ru");
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.connect();
			return true;
		} catch (Exception e) {
			Log.d(TAG, "Is not updatable");
			return false;
		}
	}

	/**
	 * Get specified metal from metal list
	 * 
	 * @param source
	 * @param rateList
	 * @return specifiedMetalList
	 */
	private List<Rate> getMetalFromList(StockItem source, List<Rate> rateList) {
		List<Rate> metalList = new ArrayList<Rate>();
		for (int i = 0; i < rateList.size(); i++) {
			String code = rateList.get(i).getSource().getCode();
			if (code.equals(source.getCode())) {
				metalList.add(rateList.get(i));
			}
		}
		return metalList;
	}

	/**
	 * Return CurrencyRate list from CBFR on current date
	 * 
	 * @return List<Rate>
	 */
	public List<Rate> getRateList() {
		if (!isUpdatableNow()) {
			return null;
		}
		Date rateDate = Calendar.getInstance().getTime();
		CurrencySaxHandler handler = new CurrencySaxHandler();
		handler.setDate(rateDate);
		handler.setResources(resources);
		return getCbrfData(URL_ENG, rateDate, handler);
	}

	private List<Rate> getCbrfData(String urlAddress, Date rateDate,
			CurrencySaxHandler handler) {

		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser parser = factory.newSAXParser();

			SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
			String date = dateFormat.format(rateDate);
			URL url = new URL(urlAddress + "?date_req=" + date);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
				return null;
			}

			InputSource courseXml = new InputSource(new InputStreamReader(
					conn.getInputStream(), "windows-1251"));
			parser.parse(courseXml, handler);
			List<Rate> currList = handler.getCurrency();
			return currList;

		} catch (MalformedURLException e) {
			Log.e(TAG, "MalformedURLException");
		} catch (IOException e) {
			Log.e(TAG, "IOException");
		} catch (SAXException e) {
			Log.e(TAG, "SAXException");
		} catch (ParserConfigurationException e) {
			Log.e(TAG, "ParserConfigurationException");
		}

		return null;
	}

	/**
	 * Return specified rate from CBFR on current date. If there is no data on
	 * the current rate return last available data
	 * 
	 * @param rt
	 * @return List<Rate>
	 */
	public List<Rate> getRate(RateType rt) {
		if (!isUpdatableNow()) {
			return null;
		}
		Date date = Calendar.getInstance().getTime();
		CurrencyDynamicSaxHandler handler = new CurrencyDynamicSaxHandler();
		handler.setResource(rt);
		List<Rate> rateList = getDynamicCbrfData(URL_DYN, date, date,
				rt.getSource(), handler);

		if (rateList.size() == 0) {
			List<Rate> rate = new ArrayList<Rate>();
			boolean rateFound = false;
			Date dateRetry = date;

			while (!rateFound) {
				Calendar calendarRetry = Calendar.getInstance();
				calendarRetry.setTime(dateRetry);
				calendarRetry.add(Calendar.DAY_OF_MONTH, -1);
				dateRetry = calendarRetry.getTime();

				rate = getDynamicRateList(rt, dateRetry, dateRetry);
				if (rate.size() != 0) {
					rateFound = true;
				}
			}
			rateList.add(rate.get(0));
		}

		return rateList;
	}

	/**
	 * Return dynamic of current rate from CBFR on period from startDate to
	 * endDate
	 * 
	 * @param rt
	 * @param startDate
	 * @param endDate
	 * @return List<Rate>
	 */
	public List<Rate> getDynamicRateList(RateType rt, Date startDate,
			Date endDate) {
		if (!isUpdatableNow()) {
			return null;
		}
		CurrencyDynamicSaxHandler handler = new CurrencyDynamicSaxHandler();
		handler.setResource(rt);
		return getDynamicCbrfData(URL_DYN, startDate, endDate, rt.getSource(),
				handler);
	}

	private List<Rate> getDynamicCbrfData(String urlAddress, Date startDate,
			Date endDate, StockItem source, CurrencyDynamicSaxHandler handler) {
		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser parser = factory.newSAXParser();

			SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
			String sDate = dateFormat.format(startDate);
			String eDate = dateFormat.format(endDate);

			CbrfUrlIdStore store = CbrfUrlIdStore.getInstance();
			Map<String, String> cbrfUrlList = store.getUrlIdMap();
			String urlId = cbrfUrlList.get(source.getCode());

			URL url = new URL(urlAddress + "?date_req1=" + sDate
					+ "&date_req2=" + eDate + "&VAL_NM_RQ=" + urlId);
			URLConnection conn = url.openConnection();

			InputSource courseXml = new InputSource(new InputStreamReader(
					conn.getInputStream(), "windows-1251"));
			parser.parse(courseXml, handler);
			List<Rate> currList = handler.getCurrency();
			return currList;

		} catch (MalformedURLException e) {
			Log.e(TAG, "MalformedURLException");
		} catch (IOException e) {
			Log.e(TAG, "IOException");
		} catch (SAXException e) {
			Log.e(TAG, "SAXException");
		} catch (ParserConfigurationException e) {
			Log.e(TAG, "ParserConfigurationException");
		}

		return null;
	}

	/**
	 * Return dynamic of current rate specific count from CBFR on period from
	 * startDate to endDate
	 * 
	 * @param startDate
	 *            , endDate, rateName
	 * @return List<Rate>
	 */
	public List<Rate> getDynamicCountRateList(RateType rateType,
			Date startDate, Date endDate, int count) {
		if (!isUpdatableNow()) {
			return null;
		}
		return getDynamicCountCbrfData(URL_DYN, startDate, endDate, rateType,
				count);
	}

	private List<Rate> getDynamicCountCbrfData(String urlAddress,
			Date startDate, Date endDate, RateType rateType, int count) {

		int yearDifference = endDate.getYear() - startDate.getYear();
		int monthDifference = endDate.getMonth() - startDate.getMonth();
		int dayDifference = endDate.getDay() - startDate.getDay();
		int difference = yearDifference * 365 + monthDifference * 30
				+ dayDifference;
		int intervalDays = difference / (count - 1);

		List<Rate> allCurrList = new ArrayList<Rate>();
		Date date = startDate;

		for (int i = 0; i < count; i++) {
			CbrfProvider cbrfData = new CbrfProvider(resources);
			List<Rate> currList = new ArrayList<Rate>();
			cbrfData.getDynamicRateList(rateType, date, date);

			if (currList.size() == 0) {
				List<Rate> curr = new ArrayList<Rate>();
				boolean rateFound = false;
				Date dateRetry = date;

				while (!rateFound) {
					Calendar calendarRetry = Calendar.getInstance();
					calendarRetry.setTime(dateRetry);
					calendarRetry.add(Calendar.DAY_OF_MONTH, 1);
					dateRetry = calendarRetry.getTime();

					curr = cbrfData.getDynamicRateList(rateType, dateRetry,
							dateRetry);
					if (curr.size() != 0) {
						rateFound = true;
					}
				}
				currList.add(curr.get(0));
			}
			allCurrList.add(currList.get(0));

			Calendar calendar = Calendar.getInstance();
			calendar.setTime(date);
			calendar.add(Calendar.DAY_OF_MONTH, intervalDays);
			date = calendar.getTime();
		}
		return allCurrList;
	}

	/**
	 * Return dynamic of metal rate from CBFR on period from startDate to
	 * endDate
	 * 
	 * @param startDate
	 * @param endDate
	 * @return List<Rate>
	 */
	public List<Rate> getDynamicMetalList(Date startDate, Date endDate) {
		if (!isUpdatableNow()) {
			return null;
		}
		MetalSaxHandler handler = new MetalSaxHandler();
		handler.setResources(resources);
		return getDynamicMetalCbrfData(URL_MET, startDate, endDate, handler);
	}

	/**
	 * Return metal rate from CBFR on specified date
	 * 
	 * @param date
	 * @return List<Rate>
	 */
	public List<Rate> getMetalListByDate(Date date) {
		if (!isUpdatableNow()) {
			return null;
		}
		MetalSaxHandler handler = new MetalSaxHandler();
		handler.setResources(resources);
		return getDynamicMetalCbrfData(URL_MET, date, date, handler);
	}

	/**
	 * Return metal rate from CBFR on current date
	 * 
	 * @return List<Rate>
	 */
	public List<Rate> getMetalList() {
		if (!isUpdatableNow()) {
			return null;
		}
		MetalSaxHandler handler = new MetalSaxHandler();
		handler.setResources(resources);
		Date date = Calendar.getInstance().getTime();

		List<Rate> rateList = getDynamicMetalCbrfData(URL_MET, date, date,
				handler);

		if (rateList.size() == 0) {
			List<Rate> rate = new ArrayList<Rate>();
			boolean rateFound = false;
			Date dateRetry = date;

			while (!rateFound) {
				Calendar calendarRetry = Calendar.getInstance();
				calendarRetry.setTime(dateRetry);
				calendarRetry.add(Calendar.DAY_OF_MONTH, -1);
				dateRetry = calendarRetry.getTime();

				rate = getMetalListByDate(dateRetry);
				if (rate.size() != 0) {
					rateFound = true;
				}
			}
			for (int i = 0; i < rate.size(); i++) {
				rateList.add(rate.get(i));
			}
		}

		return rateList;
	}

	private List<Rate> getDynamicMetalCbrfData(String urlAddress,
			Date startDate, Date endDate, MetalSaxHandler handler) {

		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser parser = factory.newSAXParser();

			SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
			String sDate = dateFormat.format(startDate);
			String eDate = dateFormat.format(endDate);

			URL url = new URL(urlAddress + "?date_req1=" + sDate
					+ "&date_req2=" + eDate);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
				return null;
			}

			InputSource courseXml = new InputSource(new InputStreamReader(
					conn.getInputStream(), "windows-1251"));
			parser.parse(courseXml, handler);
			List<Rate> currList = handler.getCurrency();
			return currList;

		} catch (MalformedURLException e) {
			Log.e(TAG, "MalformedURLException");
		} catch (IOException e) {
			Log.e(TAG, "IOException");
		} catch (SAXException e) {
			Log.e(TAG, "SAXException");
		} catch (ParserConfigurationException e) {
			Log.e(TAG, "ParserConfigurationException");
		}
		return null;
	}

	@Override
	public Rate getCurrentRate(String id) throws UnavailableValueException{
		Log.d(TAG, "Get latest rate");
		if (!isUpdatableNow()) {
			throw new UnavailableValueException();
		}

		RateTypeStore rateTypeStore = RateTypeStore.getInstance(resources);
		RateType rateType = rateTypeStore.getRateType(id);
		if (rateType == null) {
			throw new UnavailableValueException();
		}

		List<Rate> rateList = new ArrayList<Rate>();
		if (rateTypeStore.isMetal(id)) {
			List<Rate> list = getMetalList();
			rateList = getMetalFromList(rateType.getSource(), list);
		} else {
			rateList = getRate(rateType);
		}

		if (rateList.size() == 0) {
			throw new UnavailableValueException();
		}

		Rate rate = rateList.get(0);
		setAsCurrent(rate);
		rate.setDifference(getDifference(id));
		return rate;
	}

	@Override
	public List<Rate> getDynamicRates(String id, Date since, Date until) throws UnavailableValueException {
		Log.d(TAG, "Get dynamic rates");
		if (!isUpdatableNow()) {
			throw new UnavailableValueException();
		}

		RateTypeStore rateTypeStore = RateTypeStore.getInstance(resources);

		RateType rateType = rateTypeStore.getRateType(id);
		if (rateType == null) {
			throw new UnavailableValueException();
		}

		List<Rate> rateList = new ArrayList<Rate>();
		if (rateTypeStore.isMetal(id)) {
			List<Rate> list = getDynamicMetalList(since, until);
			rateList = getMetalFromList(rateType.getSource(), list);
		} else {
			rateList = getDynamicRateList(rateType, since, until);
		}

		if (rateList.size() == 0) {
			throw new UnavailableValueException();
		}

		return rateList;
	}

	@Override
	public List<Category> getCategories() {
		RateTypeStore rateTypeStore = RateTypeStore.getInstance(resources);
		return rateTypeStore.getCategories();
	}

	@Override
	public List<RateType> getRateTypeByCategory(Category category) {

		RateTypeStore rateTypeStore = RateTypeStore.getInstance(resources);
		return rateTypeStore.getRateTypesByCategory(category);
	}

	@Override
	public String getName() {
		return resources.getString(R.string.Cbrf_Name);
	}

	private double getDifference(String id) {
		Rate current = currentRate.get(id);
		Rate previous = previousRate.get(id);
		if (previous == null) {
			// try to find previous value
			try {
				Date since = new Date(current.getDate().getTime());
				since.setMonth(since.getMonth() - 1);
				List<Rate> rates = getDynamicRates(id, since, current.getDate());
				if (rates.size() > 1) {
					previous = rates.get(1);
				}
			} catch (UnavailableValueException e) {	}
		} 
		if (previous == null) {
			return 0;
		} else {
			return current.getValue() - previous.getValue();
		}
	}

	/**
	 * Try to set this Rate as current for this id.
	 * @param newRate
	 */
	private void setAsCurrent(Rate newRate) {
		if (newRate != null) {
			String id = newRate.getResource().getId();
			Rate current = currentRate.get(id);

			if (current == null) {
				// we haven't current Rate for this id yet
				currentRate.put(id, newRate);
			} else if (newRate.getDate().compareTo(current.getDate()) > 0) {
				// we have current Rate that is older than newRate
				currentRate.put(id, newRate);
				previousRate.put(id, current);
			}
		}
	}	
}